Aidemy 教師あり学習(分類)基礎
教師あり学習(分類)
データを用意する方法(1)
分類に適した架空のデータを作成するには、scikit-learn.datasetsモジュールの make_classification() 関数を使う。 教師あり学習による分類では、データとそのデータがどのクラスに属しているかを表すラベルが必要。
make_classification() 関数を用いれば任意のデータ数、ラベルの種類を引数で設定することが出来る。
n_samples - 用意するデータの個数
n_classes - クラス数。デフォルトは2
n_features - データの特徴量の個数
n_redundant - 分類に不要な特徴量(余分な特徴量)の個数
random_state - 乱数のシード(乱数のパターンを決定する要素)
code: Python
from sklearn.datasets import make_classification
import matplotlib.pyplot as plt
import matplotlib
# データX, ラベルyを生成
X, y = make_classification(n_samples=50, n_classes=2, n_features=2, n_redundant=0, random_state=0)
# データの色付け、プロット
plt.scatter(X:, 0, X:, 1, c=y, marker=".", cmap=matplotlib.cm.get_cmap(name="bwr"), alpha=0.7) plt.grid(True)
plt.show()
https://gyazo.com/3eb5fc4792504263f234f4d7281ced3f
データを用意する方法(2)
Irisデータには150個のアヤメ(花の一種)のサンプルの「がく片の長さ」「がく片の幅」「花びらの長さ」「花びらの幅」の4つの特徴量(単位はcm)と、3種の品種(0~2)が格納されている。ここでは、データの可視化のために特徴量を「がくの長さ」「花びらの長さ」の2つだけを使用する。
code: Python
from sklearn import datasets
import matplotlib.pyplot as plt
import matplotlib
# データを取得する
iris = datasets.load_iris()
# irisの0列目(がくの長さ)と2列目(花びらの長さ)を格納する
# irisのクラスラベルを格納する
y = iris.target
# データの色付け、プロット
# がくの長さを横軸に、花びらの長さを縦軸に
plt.scatter(X:, 0, X:, 1, c=y, marker=".", cmap=matplotlib.cm.get_cmap(name="cool"), alpha=0.7)
plt.xlabel("Sepal length")
plt.ylabel("Petal length")
plt.grid(True)
plt.show()
https://gyazo.com/3433304badf9193cb9afd11ef32c5acc
学習と予測
code: Python
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import train_test_split
from sklearn.datasets import make_classification
# データの生成
X, y = make_classification(n_samples=100, n_features=2, n_redundant=0, random_state=42)
# データを学習に使う分と評価の分に分ける
train_X, test_X, train_y, test_y = train_test_split(X, y, random_state=42)
# モデルの構築
model = LogisticRegression(random_state=42)
# train_Xとtrain_yを使ってモデルに学習させる
model.fit(train_X, train_y)
# test_Xに対するモデルの分類予測結果を格納する
pred_y = model.predict(test_X)
print("予測:"+ str(pred_y))
print("実際:"+ str(test_y))
--------------------------------------------------------------------------
--------------------------------------------------------------------------
ロジスティック回帰
直線でデータのカテゴリーのグループに分けることができるデータを線形分離可能なデータと呼ぶ。
ロジスティック回帰は線形分離可能なデータの境界線を学習によって見つけてデータの分類を行なう手法。
特徴としては境界線が直線になることです。そのため、二項分類などクラスの少ないデータに用いられる。 また、データがクラスに分類される確率も計算することが可能。これらの特徴から主に「天気予報の降水確率」など、分類される確率を知りたい時に用いられる。
欠点としてはトレーニングデータが線形分離可能でないと分類ができないということ。また高次元の疎なデータには適さない。 また、トレーニングデータから学習した境界線はクラスの端にあるデータのすぐそばを通るようになるため、一般化した境界線になりにくい(汎化能力が低い)ことも欠点。
code: Python
import numpy as np
import matplotlib
import matplotlib.pyplot as plt
from sklearn import datasets
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import train_test_split
from sklearn.datasets import make_classification
# データを取得
iris = datasets.load_iris()
# irisの0列目と2列目を格納
# irisのクラスラベルを格納
y = iris.target
# trainデータ、testデータの分割
train_X, test_X, train_y, test_y = train_test_split(X, y, test_size=0.3, random_state=42)
# ロジスティック回帰モデルの構築
model = LogisticRegression()
# train_Xとtrain_yを使ってモデルに学習させる
model.fit(train_X, train_y)
# test_Xに対するモデルの分類予測結果
y_pred = model.predict(test_X)
print(y_pred)
# 以下可視化の作業です
# 全データを散布図にプロットし、ラベルごとに色を分ける
plt.scatter(X:, 0, X:, 1, c=y, marker=".", cmap=matplotlib.cm.get_cmap(name="cool"), alpha=1.0) # グラフの範囲を決定
x1_min, x1_max = X:, 0.min() - 1, X:, 0.max() + 1 x2_min, x2_max = X:, 1.min() - 1, X:, 1.max() + 1 # グラフを0.02ごとに区切った時の交点の座標を格納
xx1, xx2 = np.meshgrid(np.arange(x1_min, x1_max, 0.02), np.arange(x2_min, x2_max, 0.02))
# 全てのxx1,xx2のペアに対して、学習モデルで予測を行う
# 座標(xx1, xx2)にZを描画
plt.contourf(xx1, xx2, Z, alpha=0.4, cmap=matplotlib.cm.get_cmap(name="Wistia"))
# 範囲、ラベル、タイトル、グリッドを指定
plt.xlim(xx1.min(), xx1.max())
plt.ylim(xx2.min(), xx2.max())
plt.title("classification data using LogisticRegression")
plt.xlabel("Sepal length")
plt.ylabel("Petal length")
plt.grid(True)
plt.show()
--------------------------------------------------------------------------
--------------------------------------------------------------------------
https://gyazo.com/8e1fb1a758db4f15000b4275aef76595
線形SVM
SVM(サポートベクターマシン) はロジスティック回帰と同じくデータの境界線を見つけることでデータの分類を行なう手法。 その最大の特徴はサポートベクターとよばれるベクトル。
サポートベクターはクラスごとの境界線に最も近いデータと境界線の距離のことを指す。 (厳密には距離を表すベクトルのこと)
このサポートベクターの距離の総和を最大化しようとする(この問題をマージン最大化と言う)ことによって境界線を決定する手法がSVM。
SVMは分類する境界線が二クラス間の最も離れた場所に引かれるためロジスティック回帰と比べて一般化されやすく、 データの分類予測が向上する傾向が見られる。 また、境界線の決定にはサポートベクターのみを考えればよいため、筋道がたちやすいのも特徴。
欠点としてデータ量が増えると計算量が増えてしまうため、他の手法に比べ学習や予測が遅くなる傾向があるという点が挙げられる。 また、ロジスティック回帰と同様に、入力データが線形分離可能(つまりまっすぐ境界面を引ける状態) でない限り正しく分類が行えない。
code: Python
import numpy as np
import matplotlib
import matplotlib.pyplot as plt
from sklearn import datasets
from sklearn.svm import LinearSVC
from sklearn.model_selection import train_test_split
from sklearn.datasets import make_classification
iris = datasets.load_iris()
y = iris.target
train_X, test_X, train_y, test_y = train_test_split(X, y, test_size=0.3, random_state=42)
# モデルの構築
model = LinearSVC()
# train_Xとtrain_yを使ってモデルに学習させる
model.fit(train_X, train_y)
# test_Xとtest_yを用いたモデルの正解率を出力
print(model.score(test_X, test_y))
# 以下可視化の作業です
plt.scatter(X:, 0, X:, 1, c=y, marker=".", cmap=matplotlib.cm.get_cmap(name="cool"), alpha=1.0) x1_min, x1_max = X:, 0.min() - 1, X:, 0.max() + 1 x2_min, x2_max = X:, 1.min() - 1, X:, 1.max() + 1 xx1, xx2 = np.meshgrid(np.arange(x1_min, x1_max, 0.02), np.arange(x2_min, x2_max, 0.02))
plt.contourf(xx1, xx2, Z, alpha=0.4, cmap=matplotlib.cm.get_cmap(name="Wistia"))
plt.xlim(xx1.min(), xx1.max())
plt.ylim(xx2.min(), xx2.max())
plt.title("classification data using LinearSVC")
plt.xlabel("Sepal length")
plt.ylabel("Petal length")
plt.grid(True)
plt.show()
--------------------------------------------------------------------------
1.0
--------------------------------------------------------------------------
https://gyazo.com/454cb1eb625b829e2b2cfc24899bc942
非線形SVM
特徴 前セッションの線形SVMは筋道が立てやすく、一般性も高い優秀なモデルですが、 入力データが線形分離でない限り使えないという欠点を持っていた。
非線形SVMはSVMの欠点を取り除くため開発されたモデル。
カーネル関数と呼ばれる変換式に従って数学的処理を行いデータを操作することで、 入力データが線形分離可能な状態となる場合がある。
そのような処理を行いSVMを用いるモデルが非線形SVM。
カーネル関数による操作は複雑だが、その操作の計算を行わずとも データの操作後の内積が求められれば分類を行うことが可能なので、カーネルトリックとも呼ばれる。
code: Python
from sklearn.svm import LinearSVC
from sklearn.svm import SVC
from sklearn import datasets
from sklearn.model_selection import train_test_split
from sklearn.datasets import make_gaussian_quantiles
import numpy as np
import matplotlib.pyplot as plt
import matplotlib
# データの生成
iris = datasets.load_iris()
y = iris.target
train_X, test_X, train_y, test_y = train_test_split(X, y, test_size=0.3, random_state=42)
# モデルの構築
model1 = LinearSVC()
model2 = SVC()
# train_Xとtrain_yを使ってモデルに学習させる
model1.fit(train_X, train_y)
model2.fit(train_X, train_y)
# 正解率の算出
print("線形SVM: {}".format(model1.score(test_X, test_y)))
print("非線形SVM: {}".format(model2.score(test_X, test_y)))
# 以下可視化の作業です
fig, (axL, axR) = plt.subplots(ncols=2, figsize=(10, 4))
axL.scatter(X:, 0, X:, 1, c=y, marker=".", cmap=matplotlib.cm.get_cmap(name="cool"), alpha=1.0) x1_min, x1_max = X:, 0.min() - 1, X:, 0.max() + 1 x2_min, x2_max = X:, 1.min() - 1, X:, 1.max() + 1 xx1, xx2 = np.meshgrid(np.arange(x1_min, x1_max, 0.02), np.arange(x2_min, x2_max, 0.02))
axL.contourf(xx1, xx2, Z1, alpha=0.4, cmap=matplotlib.cm.get_cmap(name="Wistia"))
axL.set_xlim(xx1.min(), xx1.max())
axL.set_ylim(xx2.min(), xx2.max())
axL.set_title("classification data using LinearSVC")
axL.set_xlabel("Sepal length")
axL.set_ylabel("Petal length")
axL.grid(True)
axR.scatter(X:, 0, X:, 1, c=y, marker=".", cmap=matplotlib.cm.get_cmap(name="cool"), alpha=1.0) x1_min, x1_max = X:, 0.min() - 1, X:, 0.max() + 1 x2_min, x2_max = X:, 1.min() - 1, X:, 1.max() + 1 xx1, xx2 = np.meshgrid(np.arange(x1_min, x1_max, 0.02), np.arange(x2_min, x2_max, 0.02))
axR.contourf(xx1, xx2, Z2, alpha=0.4, cmap=matplotlib.cm.get_cmap(name="Wistia"))
axR.set_xlim(xx1.min(), xx1.max())
axR.set_ylim(xx2.min(), xx2.max())
axR.set_title("classification data using SVC")
axR.set_xlabel("Sepal length")
axR.grid(True)
plt.show()
--------------------------------------------------------------------------
線形SVM: 1.0
非線形SVM: 1.0
--------------------------------------------------------------------------
https://gyazo.com/54ddfa7efa38e7823934f49b0eff7de9
決定木
決定木はこれまで紹介したロジスティック回帰やSVMとは違い、データの要素(説明変数)の一つ一つに着目し、その要素内でのある値を境にデータを分割していくことでデータの属するクラスを決定しようとする手法。
決定木では説明変数の一つ一つが目的変数にどのくらいの影響を与えているのかを見ることができる。 分割を繰り返すことで枝分かれしていくが、先に分割される変数ほど影響力が大きいと捉えることができる。
欠点は線形分離可能なデータは苦手であること(例えば2次元データでは境界線が斜めに引けない)と、学習が教師データに寄りすぎる(汎化されない)こと。
code: Python
import pandas as pd
from sklearn.model_selection import train_test_split
from sklearn import datasets
from sklearn.tree import DecisionTreeClassifier
import numpy as np
import matplotlib.pyplot as plt
import matplotlib
# データの生成
iris = datasets.load_iris()
y = iris.target
train_X, test_X, train_y, test_y = train_test_split(X, y, test_size=0.3, random_state=42)
# モデルの構築
model = DecisionTreeClassifier()
# モデルの学習
model.fit(train_X, train_y)
# test_Xとtest_yを用いたモデルの正解率を出力
print(model.score(test_X, test_y))
# 以下可視化の作業です
plt.scatter(X:, 0, X:, 1, c=y, marker=".", cmap=matplotlib.cm.get_cmap(name="cool"), alpha=1.0) x1_min, x1_max = X:, 0.min() - 1, X:, 0.max() + 1 x2_min, x2_max = X:, 1.min() - 1, X:, 1.max() + 1 xx1, xx2 = np.meshgrid(np.arange(x1_min, x1_max, 0.02), np.arange(x2_min, x2_max, 0.02))
plt.contourf(xx1, xx2, Z, alpha=0.4, cmap=matplotlib.cm.get_cmap(name="Wistia"))
plt.xlim(xx1.min(), xx1.max())
plt.ylim(xx2.min(), xx2.max())
plt.title("classification data using DecisionTreeClassifier")
plt.xlabel("Sepal length")
plt.ylabel("Petal length")
plt.grid(True)
plt.show()
--------------------------------------------------------------------------
0.955555555556
--------------------------------------------------------------------------
https://gyazo.com/1962084031b9f8bd37919add10d5ecfa
ランダムフォレスト
ランダムフォレストは、前述の決定木の簡易版を複数作り、分類の結果を多数決で決める手法。 複数の簡易分類器を一つの分類器にまとめて学習させる、アンサンブル学習と呼ばれる学習の種類の一手法でもある。
決定木では使用する説明変数は全て使用していたのに対し、ランダムフォレストの一つ一つの決定木はランダムに決められた少数の説明変数だけを用いてデータの属するクラスを決定しようとする。 その上で複数の簡易決定木から出力されるクラスのうちで最も多かったクラスを結果として出力する。
ランダムフォレストの特徴は、線形分離可能でない複雑な識別範囲を持つデータ集合の分類が可能な点に加え、複数の分類器を通して多数決により結果を出力するため、外れ値によって予測結果が左右されにくいことが挙げられる。
欠点としては決定木と同じように説明変数の数に対してデータの数が少ないと二分木の分割ができず、予測の精度が下がってしまう点が挙げられる。
決定木と似たような境界をしていることがわかる。これは、ランダムフォレストが、決定木アルゴリズムのアンサンブル学習(個々に学習した複数の学習器を融合させて汎化性能を向上させること)をしているため。
code: Python
import pandas as pd
from sklearn import datasets
from sklearn.model_selection import train_test_split
import numpy as np
import matplotlib.pyplot as plt
import matplotlib
# データの生成
iris = datasets.load_iris()
y = iris.target
train_X, test_X, train_y, test_y = train_test_split(X, y, test_size=0.3, random_state=42)
# モデルの読み込み(import)
from sklearn.ensemble import RandomForestClassifier
from sklearn.tree import DecisionTreeClassifier
# モデルの構築
# ランダムフォレスト
model1 = RandomForestClassifier()
# 決定木
model2 = DecisionTreeClassifier()
# モデルの学習
model1.fit(train_X, train_y)
model2.fit(train_X, train_y)
# 正解率を算出
print("ランダムフォレスト: {}".format(model1.score(test_X, test_y)))
print("決定木: {}".format(model2.score(test_X, test_y)))
# 以下可視化作業です
fig, (axL, axR) = plt.subplots(ncols=2, figsize=(10, 4))
axL.scatter(X:, 0, X:, 1, c=y, marker=".", cmap=matplotlib.cm.get_cmap(name="cool"), alpha=1.0) x1_min, x1_max = X:, 0.min() - 1, X:, 0.max() + 1 x2_min, x2_max = X:, 1.min() - 1, X:, 1.max() + 1 xx1, xx2 = np.meshgrid(np.arange(x1_min, x1_max, 0.02), np.arange(x2_min, x2_max, 0.02))
axL.contourf(xx1, xx2, Z1, alpha=0.4, cmap=matplotlib.cm.get_cmap(name="Wistia"))
axL.set_xlim(xx1.min(), xx1.max())
axL.set_ylim(xx2.min(), xx2.max())
axL.set_title("classification data using RandomForestClassifier")
axL.set_xlabel("Sepal length")
axL.set_ylabel("Petal length")
axL.grid(True)
axR.scatter(X:, 0, X:, 1, c=y, marker=".", cmap=matplotlib.cm.get_cmap(name="cool"), alpha=1.0) x1_min, x1_max = X:, 0.min() - 1, X:, 0.max() + 1 x2_min, x2_max = X:, 1.min() - 1, X:, 1.max() + 1 xx1, xx2 = np.meshgrid(np.arange(x1_min, x1_max, 0.02), np.arange(x2_min, x2_max, 0.02))
axR.contourf(xx1, xx2, Z2, alpha=0.4, cmap=matplotlib.cm.get_cmap(name="Wistia"))
axR.set_xlim(xx1.min(), xx1.max())
axR.set_ylim(xx2.min(), xx2.max())
axR.set_title("classification data using DecisionTreeClassifier")
axR.set_xlabel("Sepal length")
axR.grid(True)
plt.show()
--------------------------------------------------------------------------
ランダムフォレスト: 0.9555555555555556
決定木: 0.9555555555555556
--------------------------------------------------------------------------
https://gyazo.com/29d79a37630659830f5358585c77e7c0
k-NN
k-NNはk近傍法とも呼ばれ、予測をするデータと類似したデータをいくつか見つけ、多数決により分類結果を決める手法。 怠惰学習と呼ばれる学習の種類の一手法であり、 学習コスト(学習にかかる計算量)が0である ことが特徴。
これまでの手法とは違い、k-NNは教師データから学習するわけではなく、 予測時に教師データを直接参照 してラベルを予測する。 結果の予測を行う際の手法は以下の通り。
教師データを予測に用いるデータとの類似度で並べ直す。
分類器に設定されたk個分のデータを類似度の高い順に参照する。
参照された教師データが属するクラスのなかで最も多かったものを予測結果として出力する。
k-NNの特徴としては、前述の通り学習コストが0であること、アルゴリズムとしては比較的単純なものなのですが高い予測精度がでやすいこと、複雑な形の境界線も表現しやすいことが挙げられる。 欠点としては分類器に指定する自然数kの個数を増やしすぎると識別範囲の平均化が進み予測精度が下がってしまう点や、予測時に毎回計算を行うため教師データや予測データの量が増えると計算量が増えてしまい、低速なアルゴリズムとなってしまう点が挙げられる。
code: Python
import pandas as pd
from sklearn import datasets
from sklearn.model_selection import train_test_split
from sklearn.neighbors import KNeighborsClassifier
import numpy as np
import matplotlib.pyplot as plt
import matplotlib
# データの生成
iris = datasets.load_iris()
y = iris.target
train_X, test_X, train_y, test_y = train_test_split(X, y, test_size=0.3, random_state=42)
# モデルの構築
model = KNeighborsClassifier()
# モデルの学習
model.fit(train_X, train_y)
# 正解率の表示
print(model.score(test_X, test_y))
# 以下可視化作業です
plt.scatter(X:, 0, X:, 1, c=y, marker=".", cmap=matplotlib.cm.get_cmap(name="cool"), alpha=1.0) x1_min, x1_max = X:, 0.min() - 1, X:, 0.max() + 1 x2_min, x2_max = X:, 1.min() - 1, X:, 1.max() + 1 xx1, xx2 = np.meshgrid(np.arange(x1_min, x1_max, 0.02), np.arange(x2_min, x2_max, 0.02))
plt.contourf(xx1, xx2, Z, alpha=0.4, cmap=matplotlib.cm.get_cmap(name="Wistia"))
plt.xlim(xx1.min(), xx1.max())
plt.ylim(xx2.min(), xx2.max())
plt.title("classification data using KNeighborsClassifier")
plt.xlabel("Sepal length")
plt.ylabel("Petal length")
plt.grid(True)
plt.show()
--------------------------------------------------------------------------
0.977777777778
--------------------------------------------------------------------------
https://gyazo.com/fe588aba36ad61bdb1a5e06b11b71a65
ハイパーパラメーターとチューニング
ハイパーパラメーターとは
機械学習においても学習過程全てを自動化することは難しく、人の手でモデルを調整しなければならない場合が存在する。
ハイパーパラメーターとは 機械学習のモデルが持つパラメーターの中で人が調整をしないといけないパラメーター のこと。
チューニングとは
ハイパーパラメーターを調整することをチューニングと呼ぶ。 調整方法については直接値をモデルに入力すること以外にも、ハイパーパラメーターの値の範囲を指定することで最適な値を探してもらう方法も存在する。
scikit-learnではモデルの構築時にパラメーターに値を入力することでパラメーターのチューニングが可能。 パラメーターを入力しなかった場合、モデルごとに定められているパラメーターの初期値がそのまま値として指定される。
code: Python
# 架空のモデルClassifierを例にしたチューニング方法
model = Classifier(param1=1.0, param2=True, param3="linear")
パラメーター C
ロジスティック回帰にはCというパラメーターが存在する。 このCはモデルが学習する識別境界線が教師データの分類間違いに対してどのくらい厳しくするのかという指標になる。
Cの値が大きいほどモデルは教師データを完全に分類できるような識別線を学習するようになる。 しかし教師データに対して過剰なほどの学習を行うために過学習に陥り、訓練データ以外のデータに予測を行うと正解率が下がる場合が多くなる。
Cの値を小さくすると教師データの分類の誤りに寛容になる。 分類間違いを許容することで外れ値データに境界線が左右されにくくなりより一般化された境界線を得やすくなる。 ただし、外れ値の少ないデータでは境界線がうまく識別できていないものになってしまう場合もある。 また、極端に小さくてもうまく境界線が識別できない。
scikit-learnのロジスティック回帰モデルのCの初期値は1.0。
code: Python
import matplotlib.pyplot as plt
from sklearn.linear_model import LogisticRegression
from sklearn.datasets import make_classification
from sklearn import preprocessing
from sklearn.model_selection import train_test_split
# データの生成
X, y = make_classification(n_samples=1250, n_features=4, n_informative=2, n_redundant=2, random_state=42)
train_X, test_X, train_y, test_y = train_test_split(X, y, random_state=42)
# Cの値の範囲を設定(今回は1e-5,1e-4,1e-3,0.01,0.1,1,10,100,1000,10000)
# グラフ描画用の空リストを用意
train_accuracy = []
test_accuracy = []
for C in C_list:
model = LogisticRegression(C=C, random_state=42)
model.fit(train_X, train_y)
train_accuracy.append(model.score(train_X, train_y))
test_accuracy.append(model.score(test_X, test_y))
# グラフの準備
# semilogx()はxのスケールを10のx乗のスケールに変更する
plt.semilogx(C_list, train_accuracy, label="accuracy of train_data")
plt.semilogx(C_list, test_accuracy, label="accuracy of test_data")
plt.title("accuracy by changing C")
plt.xlabel("C")
plt.ylabel("accuracy")
plt.legend()
plt.show()
https://gyazo.com/fbe76b438761e867e8ed0f4f4b616344
パラメーター penalty
先ほどのCが分類の誤りの許容度だったのに対し、penaltyはモデルの複雑さに対するペナルティを表す。
penaltyに入力できる値は二つ、「L1」と「L2」です。 基本的には「L2」を選べば大丈夫ですが、「L1」を選ぶ方が欲しいデータが得られる場合もある。
L1
データの特徴量を削減することで識別境界線の一般化を図るペナルティ。
L2
データ全体の重みを減少させることで識別境界線の一般化を図るペナルティ。
パラメーター multi_class
multi_classは多クラス分類を行う際にモデルがどういった動作を行うかということを決めるパラメーター。 ロジスティック回帰では「ovr」、「multinomial」の2つの値が用意されている。
ovr
クラスに対して「属する/属さない」の二値で応えるような問題に適している。
multinomial
各クラスに分類される確率も考慮され、「属する/属さない」だけではなく「どれくらい属する可能性があるか」を扱う問題に適している。
パラメーター random_state
モデルは学習の際にデータをランダムな順番で処理していくが、random_stateはその順番を制御するためのパラメーター。 ロジスティック回帰モデルの場合、データによっては処理順によって大きく境界線が変わる場合がある。
また、このrandom_stateの値を固定することで同じデータでの学習結果を保存することができる。 当講座でも実行時に結果が変わらないようにrandom_stateの値は基本的に固定している。
当講座で用いているデータはrandom_stateを変えても結果があまり変わらないが、実際に用いる場合にはデータの再現性も考えてrandom_stateの値を固定するとよいだろう。
パラメーター C
SVMにもロジスティック回帰と同様に分類の誤りの許容度を示すCがパラメーターとして定義されている。 使い方もロジスティック回帰と同様。
SVMはロジスティック回帰に比べてCによるデータのラベルの予測値変動が激しい。 SVMのアルゴリズムはロジスティック回帰にくらべてより一般化された境界線を得るため、誤りの許容度が上下するとサポートベクターが変化し、ロジスティック回帰よりも正解率が上下することになる。
線形SVMモデルではCの初期値は1.0。
code: Python
import matplotlib.pyplot as plt
from sklearn.linear_model import LogisticRegression
from sklearn.svm import LinearSVC
from sklearn.datasets import make_classification
from sklearn import preprocessing
from sklearn.model_selection import train_test_split
# データの生成
X, y = make_classification(n_samples=1250, n_features=4, n_informative=2, n_redundant=2, random_state=42)
train_X, test_X, train_y, test_y = train_test_split(X, y, random_state=42)
# Cの値の範囲を設定(今回は1e-5,1e-4,1e-3,0.01,0.1,1,10,100,1000,10000)
# グラフ描画用の空リストを用意
svm_train_accuracy = []
svm_test_accuracy = []
log_train_accuracy = []
log_test_accuracy = []
for C in C_list:
model1 = LinearSVC(C=C, random_state=42)
model1.fit(train_X, train_y)
svm_train_accuracy.append(model1.score(train_X, train_y))
svm_test_accuracy.append(model1.score(test_X, test_y))
model2 = LogisticRegression(C=C, random_state=42)
model2.fit(train_X, train_y)
log_train_accuracy.append(model2.score(train_X, train_y))
log_test_accuracy.append(model2.score(test_X, test_y))
# グラフの準備
# semilogx()はxのスケールを10のx乗のスケールに変更する
fig = plt.figure()
plt.subplots_adjust(wspace=0.4, hspace=0.4)
ax = fig.add_subplot(1, 1, 1)
ax.grid(True)
ax.set_title("SVM")
ax.set_xlabel("C")
ax.set_ylabel("accuracy")
ax.semilogx(C_list, svm_train_accuracy, label="accuracy of train_data")
ax.semilogx(C_list, svm_test_accuracy, label="accuracy of test_data")
ax.legend()
ax.plot()
plt.show()
fig2 =plt.figure()
ax2 = fig2.add_subplot(1, 1, 1)
ax2.grid(True)
ax2.set_title("LogisticRegression")
ax2.set_xlabel("C")
ax2.set_ylabel("accuracy")
ax2.semilogx(C_list, log_train_accuracy, label="accuracy of train_data")
ax2.semilogx(C_list, log_test_accuracy, label="accuracy of test_data")
ax2.legend()
ax2.plot()
plt.show()
https://gyazo.com/36194081060b3f34394dbc1498ecdf73https://gyazo.com/c56e5b15284ae141569b834f75c612c0
パラメーター penalty
ロジスティック回帰同様に線形SVMにもpenaltyのパラメーターがある。 設定できる値も同じく、 "L1" と "L2" 。
パラメーター multi_class
multi_classは多項分類を行う際にモデルがどういった動作を行うかということを決めるパラメーター。 線形SVMでは「ovr」、「crammer_singer」の2つの値が用意されている。 基本的にはovrの方が動作が軽く結果が良い。
パラメーター random_state
結果の固定に用いられるrandom_stateだが、SVMに関してはサポートベクターの決定にも関わる。 最終的に学習する境界線はほぼ同じになるものの、わずかながら差異が出ることに留意する。
パラメーター C
線形分離可能でないデータを扱う場合SVMのSVCというモジュールを使う。 SVCでもロジティック回帰、SVMと同様にパラメーターCが存在する。
非線形SVMではCのことをソフトマージンのペナルティと呼ぶ。 学習時に分類の誤りをどの程度許容するかを指定するパラメーター。
code: Python
import matplotlib.pyplot as plt
from sklearn.svm import SVC
from sklearn.datasets import make_gaussian_quantiles
from sklearn import preprocessing
from sklearn.model_selection import train_test_split
# データの生成
X, y = make_gaussian_quantiles(n_samples=1250, n_features=2, random_state=42)
train_X, test_X, train_y, test_y = train_test_split(X, y, random_state=42)
# Cの値の範囲を設定(今回は1e-5,1e-4,1e-3,0.01,0.1,1,10,100,1000,10000)
# グラフ描画用の空リストを用意
train_accuracy = []
test_accuracy = []
for C in C_list:
model = SVC(C=C)
model.fit(train_X, train_y)
train_accuracy.append(model.score(train_X, train_y))
test_accuracy.append(model.score(test_X, test_y))
# グラフの準備
# semilogx()はxのスケールを10のx乗のスケールに変更する
plt.semilogx(C_list, train_accuracy, label="accuracy of train_data")
plt.semilogx(C_list, test_accuracy, label="accuracy of test_data")
plt.title("accuracy with changing C")
plt.xlabel("C")
plt.ylabel("accuracy")
plt.legend()
plt.show()
https://gyazo.com/be625d0d5291717660d4f4a6dadc08a0
パラメーター kernel
パラメーターkernelは非線形SVMの中でも特に重要なパラメーターであり、受け取ったデータを操作して分類しやすい形にするための関数を定義するパラメーター。
linear、rbf、poly、sigmoid、precomputedの5つを値としてとることができる。デフォルトはrbf。
linearは線形SVMであり、LinearSVCとほぼ同じ。特殊な理由がない限りはLinearSVCを使う。
rbf、polyは立体投影のようなもの。rbfは他のものに比べ比較的高い正解率が出ることが多いので通常はデフォルトであるrbfを使用する。
precomputedはデータが前処理によってすでに整形済みの場合に用いる。
sigmoidはロジスティック回帰モデルと同じ処理を行う。
パラメーター decision_function_shape
decision_function_shape はSVCにおけるmulti_classパラメーターのようなもの。
ovo、ovrの2つの値が用意されている。
ovoはクラス同士のペアを作り、そのペアでの2項分類を行い多数決で属するクラスを決定するという考え方。(総当たり)
ovrは一つのクラスとそれ以外という分類を行い多数決で属するクラスを決定する。
ovoの方は計算量が多くデータの量の増大によっては動作が重くなることが考えられる。
パラメーター random_state
データの処理順に関係するパラメーター。 予測結果を再現するために、学習の段階では固定することを推奨。
機械学習を実際に行う時には乱数を生成するための生成器を指定する方法がある。
code: Python
import numpy as np
from sklearn.svm import SVC
from sklearn.datasets import make_classification
from sklearn import preprocessing
from sklearn.model_selection import train_test_split
# データの生成
X, y = make_classification(n_samples=1250, n_features=4, n_informative=2, n_redundant=2, random_state=42)
train_X, test_X, train_y, test_y = train_test_split(X, y, random_state=42)
# 乱数生成器の構築
random_state = np.random.RandomState()
# モデルの構築
model = SVC(random_state=random_state)
# モデルの学習
model.fit(train_X, train_y)
# テストデータに対する正解率を出力
print(model.score(test_X, test_y))
--------------------------------------------------------------------------
0.948881789137
--------------------------------------------------------------------------
パラメーター max_depth
max_depth は学習時にモデルが学習する木の深さの最大値を表すパラメーター。
max_depthの値が設定されていない時、木は教師データの分類がほぼ終了するまでデータを分する。
このため教師データを過剰に信頼し学習した一般性の低いモデルとなってしまう。
また、値が大きすぎても同じように分類が終了した段階で木の成長は止まるので上記の状態と同じになる。
max_depth を設定し木の高さを制限することを決定木の枝刈りと呼ぶ。
code: Python
import matplotlib.pyplot as plt
from sklearn.datasets import make_classification
from sklearn.tree import DecisionTreeClassifier
from sklearn.model_selection import train_test_split
# データの生成
X, y = make_classification(n_samples=1000, n_features=5, n_informative=3, n_redundant=0, random_state=42)
train_X, test_X, train_y, test_y = train_test_split(X, y, random_state=42)
# max_depthの値の範囲(1から10)
# 正解率を格納するからリストを作成
accuracy = []
# max_depthを変えながらモデルを学習
for max_depth in depth_list:
model = DecisionTreeClassifier(max_depth=max_depth, random_state=42)
model.fit(train_X, train_y)
accuracy.append(model.score(test_X, test_y))
# グラフのプロット
plt.plot(depth_list, accuracy)
plt.xlabel("max_depth")
plt.ylabel("accuracy")
plt.title("accuracy by changing max_depth")
plt.show()
https://gyazo.com/c95694fa3586006816f5650a451973dd
パラメーター random_state
random_state は学習結果の保持だけではなく、決定木の学習過程に直接関わるパラメーター。
決定木の分割は分割を行う時点でよくデータの分類を説明できる要素の値を見つけ、データの分割を行うが、そのような値の候補はたくさん存在するため、 random_state による乱数の生成により、その候補を決めている。
パラメーター n_estimators
ランダムフォレストの特徴として 複数の簡易決定木による多数決で結果が決定される というものが挙げられるが、その簡易決定木の個数を決めるのがこの n_estimators というパラメーター。
code: Python
import matplotlib.pyplot as plt
from sklearn.datasets import make_classification
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import train_test_split
# データの生成
X, y = make_classification(n_samples=1000, n_features=4, n_informative=3, n_redundant=0, random_state=42)
train_X, test_X, train_y, test_y = train_test_split(X, y, random_state=42)
# n_estimatorsの値の範囲(1から20)
# 正解率を格納するからリストを作成
accuracy = []
# n_estimatorsを変えながらモデルを学習
for n_estimators in n_estimators_list:
model = RandomForestClassifier(n_estimators=n_estimators, random_state=42)
model.fit(train_X, train_y)
accuracy.append(model.score(test_X, test_y))
# グラフのプロット
plt.plot(n_estimators_list, accuracy)
plt.title("accuracy by n_estimators increasement")
plt.xlabel("n_estimators")
plt.ylabel("accuracy")
plt.show()
https://gyazo.com/026160382f6471d43ccfc36fb7e99cd8
パラメーター max_depth
ランダムフォレストは簡易決定木を複数作るので決定木に関するパラメーターを設定することが可能。
max_depthは決定木に関するパラメーターだが、ランダムフォレストにおいては 通常の決定木より小さな値を入力する。
簡易決定木の分類の多数決というアルゴリズムであるため一つ一つの決定木に対して厳密な分類を行うより着目要素を絞り俯瞰的に分析を行うことで学習の効率の良さと高い精度を保つことができる。
パラメーター random_state
random_state はランダムフォレストにおいても重要なパラメーター。
ランダムフォレストの名前の通り結果の固定のみならず、決定木のデータの分割や用いる要素の決定など多くの場面で乱数が寄与するこの手法ではこのパラメーターによって分析結果が大きく異なる。
code: Python
import matplotlib.pyplot as plt
from sklearn.datasets import make_classification
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import train_test_split
# データの生成
X, y = make_classification(n_samples=1000, n_features=4, n_informative=3, n_redundant=0, random_state=42)
train_X, test_X, train_y, test_y = train_test_split(X, y, random_state=42)
# r_seedsの値の範囲(0から99)
# 正解率を格納するからリストを作成
accuracy = []
# random_stateを変えながらモデルを学習
for seed in r_seeds:
model = RandomForestClassifier(random_state=seed)
model.fit(train_X, train_y)
accuracy.append(model.score(test_X, test_y))
# グラフのプロット
plt.plot(r_seeds, accuracy)
plt.xlabel("seed")
plt.ylabel("accuracy")
plt.title("accuracy by changing seed")
plt.show()
https://gyazo.com/2baf5fad3b9d69bff89b5671af759846
パラメーター n_neighbors
n_neighbors はk-NNのkの値のことです。 つまり、結果予測の際に使う類似データの個数を決めるパラメーターです。
n_neighbors の数が多すぎると類似データとして選ばれるデータの類似度に幅が出るため、分類範囲の狭いカテゴリーがうまく分類されないということが起こります。
code: Python
import matplotlib.pyplot as plt
from sklearn.datasets import make_classification
from sklearn.neighbors import KNeighborsClassifier
from sklearn.model_selection import train_test_split
# データの生成
X, y = make_classification(n_samples=1000, n_features=4, n_informative=3, n_redundant=0, random_state=42)
train_X, test_X, train_y, test_y = train_test_split(X, y, random_state=42)
# n_neighborsの値の範囲(1から10)
# 正解率を格納するからリストを作成
accuracy = []
# n_neighborsを変えながらモデルを学習
for k in k_list:
model = KNeighborsClassifier(n_neighbors=k)
model.fit(train_X, train_y)
accuracy.append(model.score(test_X, test_y))
# グラフのプロット
plt.plot(k_list, accuracy)
plt.xlabel("n_neighbor")
plt.ylabel("accuracy")
plt.title("accuracy by changing n_neighbor")
plt.show()
https://gyazo.com/33a8617d588952a8ce84a83728ea4546
グリッドサーチ
全てのパラメーターを都度変えて結果を確認するのは時間と手間がかかる。
そこで、パラメーターの範囲を指定して一番結果の良かったパラメーターセットを計算機に見つけてもらうという方法を使う。 主な方法は2つ、グリッドサーチとランダムサーチ。
グリッドサーチは調整したいハイパーパラメーターの値の候補を明示的に複数指定し、パラメーターセットを作成し、その時のモデルの評価を繰り返すことでモデルとして最適なパラメーターセットを作成するために用いられる方法。
値の候補を明示的に指定するためパラメーターの値に文字列や整数、True or Falseといった数学的に連続ではない値をとるパラメーターの探索に向いていr。 ただしパラメーターの候補を網羅するようにパラメーターセットが作成されるため多数のパラメーターを同時にチューニングするのには不向き。
code: Python
import scipy.stats
from sklearn.datasets import load_digits
from sklearn.svm import SVC
from sklearn.model_selection import GridSearchCV
from sklearn.model_selection import train_test_split
from sklearn.metrics import f1_score
data = load_digits()
train_X, test_X, train_y, test_y = train_test_split(data.data, data.target, random_state=42)
# パラメーターの値の候補を設定
max_score = 0
best_param = None
# グリッドサーチでパラメーターサーチ
for model, param in model_param_set_grid.items():
clf = GridSearchCV(model, param)
clf.fit(train_X, train_y)
pred_y = clf.predict(test_X)
score = f1_score(test_y, pred_y, average="micro")
if max_score < score:
max_score = score
best_model = model.__class__.__name__
best_param = clf.best_params_
print("パラメーター:{}".format(best_param))
print("ベストスコア:",max_score)
svm = SVC()
svm.fit(train_X, train_y)
print()
print('調整なし')
print(svm.score(test_X, test_y))
--------------------------------------------------------------------------
パラメーター:{'C': 0.0001, 'decision_function_shape': 'ovr', 'kernel': 'poly', 'random_state': 42}
調整なし
0.5222222222222223
--------------------------------------------------------------------------
ランダムサーチ
グリッドサーチは値の候補を指定してその上でパラメーターを調整した。
ランダムサーチはパラメーターが取りうる値の範囲を指定し、確率で決定されたパラメーターセットを用いてモデルの評価を行うことを繰り返すことによって最適なパラメーターセットを探す方法。
値の範囲の指定はパラメーターの確率関数を指定するというものになる。
パラメーターの確率関数としてscipy.statsモジュールの確率関数がよく用いられる。
code: Python
import scipy.stats
from sklearn.datasets import load_digits
from sklearn.svm import SVC
from sklearn.model_selection import RandomizedSearchCV
from sklearn.model_selection import train_test_split
from sklearn.metrics import f1_score
data = load_digits()
train_X, test_X, train_y, test_y = train_test_split(data.data, data.target, random_state=42)
# パラメーターの値の候補を設定
model_param_set_random = {SVC(): {
"C": scipy.stats.uniform(0.00001, 1000),
"random_state": scipy.stats.randint(0, 100)
}}
max_score = 0
best_param = None
# ランダムサーチでパラメーターサーチ
for model, param in model_param_set_random.items():
clf = RandomizedSearchCV(model, param)
clf.fit(train_X, train_y)
pred_y = clf.predict(test_X)
score = f1_score(test_y, pred_y, average="micro")
if max_score < score:
max_score = score
best_param = clf.best_params_
print("パラメーター:{}".format(best_param))
print("ベストスコア:",max_score)
svm = SVC()
svm.fit(train_X, train_y)
print()
print('調整なし')
print(svm.score(test_X, test_y))
--------------------------------------------------------------------------
パラメーター:{'C': 428.83484433636613, 'decision_function_shape': 'ovr', 'kernel': 'poly', 'random_state': 62}
調整なし
0.5222222222222223
--------------------------------------------------------------------------